通过本案例,你将学会:
物流行业的数据分析通常围绕四个关键维度展开:
| 维度 | 核心指标 | 分析目标 |
|---|---|---|
| 时效性 | 按时交货率 | 配送服务是否达标 |
| 反馈 | 合格/返修/拒货占比与分母 | 哪些组合需要进一步质检 |
| 区域 | 各区域表现对比 | 识别薄弱区域 |
| 货品 | 各货品销售与反馈 | 识别问题货品 |
本案例的数据清洗包含以下关键步骤:
drop_duplicates() 保留首次出现的记录dropna() 删除含缺失值的行drop(columns=['订单行']) 移除无用字段reset_index(drop=True) 更新行索引data_wuliu.csv 用于教学平台固定练习;公开教材中的拓展案例使用课程数据下载入口提供的上市公司基本信息和股票行情文件。| 阶段 | 学习步骤 | 应得到什么 |
|---|---|---|
| 1 数据要求 | 核对文件、期间、字段、单位与授权 | 数据字典与行数 |
| 2 质量检查 | 检查重复、缺失、类型与金额口径 | 清洗前后核对表 |
| 3 描述聚合 | 按月份、区域、货品汇总 | 带样本量的聚合表 |
| 阶段 | 学习步骤 | 应得到什么 |
|---|---|---|
| 4 手工核对 | 重新计算一小部分数据 | 差异记录或一致结论 |
| 5 解释结果 | 区分观察结果、待验证假设与后续建议 | 一条附带适用条件的建议 |
data、number_new、data1、data_month 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。# 注:data_wuliu.csv数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import os # 导入操作系统接口模块
import pandas as pd # 导入Pandas数据分析库
import numpy as np # 导入NumPy数值计算库
import matplotlib.pyplot as plt # 导入Matplotlib绘图库
plt.rcParams['font.sans-serif'] = 'SimHei' ## 设置中文显示
## 数据清洗部分
data = pd.read_csv('data_wuliu.csv',encoding='gbk')
#删除重复记录
data.drop_duplicates(keep='first',inplace=True) #keep='first'默认值,保留第一次出现的重复记录
#删除缺失值
data.dropna(axis=0,how='any',inplace=True)
#删除订单行
data.drop(columns=['订单行'],inplace=True,axis=1)
print(data.info()) # 输出数据框基本信息
#更新索引(drop=True:把原来的索引index列删除,重置index)
data.reset_index(drop=True,inplace=True)
def data_deal(number): # 定义函数data_deal
if number.find('万元')!= -1:#找到带有万元的,取出数字,去掉逗号,转成float,*10000
number_new = float(number[:number.find('万元')].replace(',',''))*10000 # 替换数据中的指定值
pass # 占位符,暂不执行任何操作
else: #找到带有元的,删除元,删除逗号,转成float
number_new = float(number.replace('元','').replace(',','')) # 替换数据中的指定值
pass # 占位符,暂不执行任何操作
return number_new # 返回计算结果
data['销售金额'] = data['销售金额'].map(data_deal) # 对数据进行映射转换
print(data.describe()) # 输出描述性统计信息
#销售金额为0的情况,删除
data = data[data['销售金额']!=0].copy()
data['销售时间'] = pd.to_datetime(data['销售时间']) # 转换为日期时间格式
data['交货时间'] = pd.to_datetime(data['交货时间']) # 转换为日期时间格式
data['销售月份'] = data['销售时间'].dt.month # 提取日期时间属性
data['交货月份'] = data['交货时间'].dt.month # 提取日期时间属性
#1.配送是否存在问题研究
#月份维度
data['货品交货状况'] = data['货品交货状况'].str.strip()
data1 = data.groupby(['销售月份','货品交货状况']).size().unstack() #unstack()把行索引变成列索引
data1['按时交货率'] = data1['按时交货']/(data1['按时交货']+data1['晚交货']) # 提取按时交货列作为data1['按时交货率']变量
print("月份交货状况:\n",data1) # 输出月份交货状况:\n
data1 = data.groupby(['销售区域','货品交货状况']).size().unstack() # 按指定列分组聚合
data1['按时交货率'] = data1['按时交货']/(data1['按时交货']+data1['晚交货']) # 提取按时交货列作为data1['按时交货率']变量
data1 = data1.sort_values(data1.columns[-1],ascending=False) # 按指定列排序
print("销售区域交货状况:\n",data1) # 输出销售区域交货状况:\n
#运行后按地区报告样本量与按时率;未核对置信区间和服务目标前不指定优劣地区
#货品维度
data1 = data.groupby(['货品','货品交货状况']).size().unstack()
data1['按时交货率'] = data1['按时交货']/(data1['按时交货']+data1['晚交货']) # 提取按时交货列作为data1['按时交货率']变量
data1 = data1.sort_values(data1.columns[-1],ascending=False) # 按指定列排序
print("不同货品交货状况:\n",data1) # 输出不同货品交货状况:\n
#运行后按货品报告样本量与按时率;未看到实际输出前不指定最低货品
#货品和销售区域结合
data1 = data.groupby(['货品','销售区域','货品交货状况']).size().unstack()
data1['按时交货率'] = data1['按时交货']/(data1['按时交货']+data1['晚交货']) # 提取按时交货列作为data1['按时交货率']变量
data1 = data1.sort_values(data1.columns[-1],ascending=False) # 按指定列排序
print("货品和销售区域结合的交货状况:\n",data1) # 输出货品和销售区域结合的交货状况:\n
#2.是否存在尚有潜力的销售区域问题研究
#月份维度
data_month = data.groupby(['销售月份','货品'])['数量'].sum().unstack()
print("月份维度货品的销售情况:\n",data_month) # 输出月份维度货品的销售情况:\n
# 绘制各个月份的销售情况
data_month.plot(kind='line',figsize=(12,6))
plt.title('各个月份的销售情况') # 设置图表标题
plt.savefig("1.png") # 保存图形至文件
#运行后依据月份×货品聚合表描述峰值;营销或新市场只能列为待验证假设
#销售区域维度
data_region = data.groupby(['销售区域','货品'])['数量'].sum().unstack()
print("销售区域货品的销售情况:\n",data_region) # 输出销售区域货品的销售情况:\n
#运行后报告区域×货品样本量与销量;未核对成本、时效和对照组前不提出增减投入
#月份和区域结合
data_month_region = data.groupby(['销售月份','销售区域','货品'])['数量'].sum().unstack()
data_month_region['货品2'] # 查看货品2在各月份和区域的销售数量
#交叉表只能定位峰值所在区域;营销归因与加大投入需另设对照验证
#3.商品是否存在质量问题研究
data['货品用户反馈'] = data['货品用户反馈'].str.strip()
data3 = data.groupby(['货品','销售区域'])['货品用户反馈'].value_counts().unstack() # 按指定列分组聚合
data3['拒货率'] = data3['拒货']/data3.sum(axis=1) # 计算总和
data3['合格率'] = data3['质量合格']/data3.sum(axis=1) # 计算总和
data3['返修率'] = data3['返修']/data3.sum(axis=1) # 计算总和
# 按合格率、返修率、拒货率降序排列以识别质量问题货品
data3.sort_values(['合格率','返修率','拒货率'],ascending=False)
#运行后报告各货品×区域的样本量、合格率、返修率与拒货率;未通过阈值和连续期核对前不作质量或投入结论运行后核对:核对 data、number_new、data1、data_month 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。
拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。
原始数据中销售金额的格式不统一,需要转换为数值类型:
| 原始格式 | 转换逻辑 | 转换结果 |
|---|---|---|
'5万元' |
提取数字 × 10000 | 50000.0 |
'12,345元' |
删除逗号和’元’ | 12345.0 |
'1,234.5万元' |
删除逗号 × 10000 | 12345000.0 |
def data_deal(number):
"""处理销售金额,统一转换为元(float类型)"""
if number.find('万元') != -1:
# 包含'万元':提取数字部分,去逗号,乘以10000
number_new = float(
number[:number.find('万元')].replace(',', '')
) * 10000
else:
# 包含'元':直接去掉'元'和逗号
number_new = float(
number.replace('元', '').replace(',', '')
)
return number_new
# 使用 map() 将转换函数应用到整列
data['销售金额'] = data['销售金额'].map(data_deal)将字符串类型的日期转换为 datetime,并提取月份特征:
pd.to_datetime() 将字符串解析为日期对象.dt.month 提取月份数值.copy() 创建副本,避免 SettingWithCopyWarning配送时效性是物流服务的核心 KPI,分析从三个维度切入:
按月统计按时交货 vs 晚交货的订单数,计算按时交货率:
groupby().size() 统计每个分组的记录数unstack() 将行索引转为列索引,形成交叉表按销售区域计算按时交货率,并降序排列以快速识别问题区域:
判读规则(待运行):只有在同期口径、分组样本量足够且缺失率达标时,才把按时率最低地区列入待诊断名单;当前不报告具体地区。
进一步细分到货品层面,识别哪些货品的配送问题最严重:
判读规则(待运行):先报告每种货品的样本量与按时率,再对最低组进行原因分解;未看到实际输出前不指定某一货品。
将货品和区域两个维度结合,精确定位问题组合:
交叉分析能精确定位「哪个货品在哪个区域」出了问题。
从月份维度分析各货品的销售数量变化趋势:
['数量'].sum() 对销售数量求和unstack() 将货品从行索引转为列使用折线图展示各货品销售数量随月份的变化:
可验证假设:若某货品在特定月份出现销量峰值,营销投放只是候选解释之一;还需同期活动记录、价格、供给与基线对照才能识别归因。
分析各货品在不同销售区域的覆盖情况:
待运行输出:
结合月份和区域,判断货品2销量增长的来源:
验证设计:交叉表只能定位“峰值发生在哪个地区”,不能证明营销造成增长。应补充地区×月份的投放、价格与库存记录,再以未投放的可比地区作对照;未通过验证前不增加投入。
基于用户反馈数据,计算三个质量指标:
| 指标 | 计算公式 | 期望方向 |
|---|---|---|
| 合格率 | 质量合格数 / 总数 | 越高越好 |
| 返修率 | 返修数 / 总数 | 越低越好 |
| 拒货率 | 拒货数 / 总数 | 越低越好 |
# 清洗用户反馈字段
data['货品用户反馈'] = data['货品用户反馈'].str.strip()
# 按货品和区域分组,统计各类反馈数量
data3 = data.groupby(
['货品', '销售区域']
)['货品用户反馈'].value_counts().unstack()
# 计算三大质量指标
data3['拒货率'] = data3['拒货'] / data3.sum(axis=1)
data3['合格率'] = data3['质量合格'] / data3.sum(axis=1)
data3['返修率'] = data3['返修'] / data3.sum(axis=1)
# 按合格率降序排列
data3 = data3.sort_values(
['合格率', '返修率', '拒货率'], ascending=False
)
print(data3)| 技术要点 | 对应方法 |
|---|---|
| 数据清洗 | drop_duplicates(), dropna(), str.strip() |
| 数据转换 | 自定义函数 + map() |
| 日期处理 | pd.to_datetime(), .dt.month |
| 分组聚合 | groupby().size(), groupby().sum() |
| 交叉分析 | unstack(), value_counts() |
| 多列排序 | sort_values() |
| 可视化 | DataFrame.plot(kind='line') |
case 文件;中国本地解答读取 stock_basic_data.h5/stock_basic_info 与 stock_price_pre_adjusted.h5/data,并标准化为 ts_code/name/industry/area/trade_date/close/volimport pandas as pd # 导入表格库处理平台订单表
import numpy as np # 导入有限值与整数检查要求工具
def validate_platform_quantities(logistics_orders): # 定义销售数量质量要求
required={'销售时间','销售区域','货品','货品交货状况','货品用户反馈','数量'} # 对齐平台中文字段要求
if not required<=set(logistics_orders.columns): raise ValueError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 缺列时停止
quantity_numeric=pd.to_numeric(logistics_orders['数量'],errors='coerce') # 将数量显式解析为数值
quantity_audit={'missing':int(logistics_orders['数量'].isna().sum())} # 统计原始缺失数量
quantity_audit['nonnumeric']=int(logistics_orders['数量'].notna().mul(quantity_numeric.isna()).sum()) # 统计非数字数量
finite_mask=quantity_numeric.notna()&np.isfinite(quantity_numeric.fillna(0)) # 标记可继续检查的有限值
quantity_audit['nonfinite']=int(quantity_numeric.notna().mul(~np.isfinite(quantity_numeric.fillna(0))).sum()) # 统计无穷值
quantity_audit['nonintegral']=int(finite_mask.mul(~np.isclose(quantity_numeric.fillna(0),np.round(quantity_numeric.fillna(0)))).sum()) # 统计非整数
quantity_audit['nonpositive']=int(finite_mask.mul(quantity_numeric.fillna(0).le(0)).sum()) # 统计非正数量
quantity_audit['policy']='销售数量必须为正整数;退货须用单独字段记录' # 记录数量处理规则
if any(quantity_audit[key] for key in ['missing','nonnumeric','nonfinite','nonintegral','nonpositive']): raise ValueError(f'输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 任一违规即停止
prepared_orders=logistics_orders.assign(数量=quantity_numeric.astype('int64')) # 检查要求通过后固定整数类型
return prepared_orders,quantity_audit # 返回合格订单与质量检查def summarize_platform_logistics(logistics_orders): # 汇总同一平台订单快照
prepared_orders,quantity_audit=validate_platform_quantities(logistics_orders) # 先执行数量质量要求
required=['销售时间','销售区域','货品','货品交货状况','货品用户反馈','数量'] # 固定聚合字段
clean_orders=prepared_orders.dropna(subset=required).drop_duplicates().copy() # 排除关键缺失与完全重复
if clean_orders.empty: raise ValueError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 空样本时停止
clean_orders['销售时间']=pd.to_datetime(clean_orders['销售时间']) # 统一销售时间类型
clean_orders['销售月份']=clean_orders['销售时间'].dt.to_period('M').astype(str) # 构造月度键
monthly=clean_orders.groupby('销售月份').agg(rows=('数量','size'),quantity=('数量','sum')) # 汇总月度行数与销量
regional=clean_orders.groupby(['销售区域','货品交货状况']).size().unstack(fill_value=0) # 汇总地区配送状态
product=clean_orders.groupby(['货品','货品交货状况']).size().unstack(fill_value=0) # 汇总货品配送状态
feedback=clean_orders.groupby(['货品','销售区域','货品用户反馈']).size().unstack(fill_value=0) # 统计分类反馈
feedback_rate=feedback.div(feedback.sum(axis=1),axis=0) # 按货品与地区分母计算反馈占比
if feedback_rate.isna().any().any(): raise ValueError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 零分母时停止
first_region=sorted(clean_orders['销售区域'].astype(str).unique())[0] # 固定首个地区核对
region_slice=clean_orders.loc[clean_orders['销售区域'].astype(str).eq(first_region)].groupby('货品')['数量'].sum() # 独立复算地区货品量
clean_quantity=clean_orders['数量'].sum() # 独立计算清洗后总销量
if clean_quantity!=monthly['quantity'].sum(): raise AssertionError('quantity reconciliation failed') # 核对月度汇总守恒
region_quantity=clean_orders.loc[clean_orders['销售区域'].astype(str).eq(first_region),'数量'].sum() # 独立计算地区总量
if region_quantity!=region_slice.sum(): raise AssertionError('region quantity reconciliation failed') # 核对地区分组守恒
reconciliation={'raw_rows':len(logistics_orders),'clean_rows':len(clean_orders),'clean_quantity':clean_quantity,'region':first_region,'region_quantity':region_quantity} # 形成检查摘要
return clean_orders,monthly,regional,product,feedback_rate,region_slice,reconciliation,quantity_audit # 返回聚合与检查依据from pathlib import Path # 导入路径工具绑定规定根目录
import pandas as pd # 导入表格库以读取并标准化HDF字段
import numpy as np # 导入有限值检查要求工具
root=Path('/home/ubuntu/r2_data_mount/data/stock') # 指定中国股票数据目录
basic_path=root/'stock_basic_data.h5' # 绑定公司基础信息HDF快照
price_path=root/'stock_price_pre_adjusted.h5' # 绑定前复权行情HDF快照
if not basic_path.exists() or not price_path.exists(): raise FileNotFoundError('未找到课程数据文件,请从课程数据下载入口获取并核对文件位置') # 缺失时如实停止
basic=pd.read_hdf(basic_path,key='stock_basic_info',columns=['order_book_id','symbol','industry_name','province']).rename(columns={'order_book_id':'raw_code','symbol':'name','industry_name':'industry','province':'area'}) # 只读取对象筛选字段并标准化
basic['ts_code']=basic['raw_code'].str.replace('.XSHG','.SH',regex=False).str.replace('.XSHE','.SZ',regex=False) # 统一证券代码后缀
basic['area']=basic['area'].astype('string').str.replace(r'[省市]$','',regex=True) # 统一省市简称
required_basic={'ts_code','name','industry','area'} # 定义基础信息字段要求
if not required_basic<=set(basic.columns): raise ValueError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 字段结构 不符时停止
industry_pattern='交通运输|仓储|邮政|快递|道路运输|水上运输|航空运输|装卸搬运|多式联运' # 排除运输设备制造等非物流运营行业
focus=basic.loc[basic['industry'].str.contains(industry_pattern,na=False) & basic['area'].isin(['上海','江苏','浙江','安徽'])].sort_values('ts_code').head(12) # 按固定规则选取长三角物流运营公司
if focus.empty: raise ValueError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 空对象时停止
focus_raw_codes=focus['raw_code'].tolist() # 保留HDF原始代码以高效筛选行情
prices=pd.read_hdf(price_path,key='data',where='order_book_id in focus_raw_codes',columns=['close','volume']).reset_index().rename(columns={'order_book_id':'ts_code','date':'trade_date','volume':'vol'}) # 按对象读取并标准化行情字段
prices['ts_code']=prices['ts_code'].str.replace('.XSHG','.SH',regex=False).str.replace('.XSHE','.SZ',regex=False) # 统一行情证券代码后缀
prices=prices.loc[pd.to_datetime(prices['trade_date']).between('2019-01-01','2025-12-31')].copy() # 固定观察期,便于比较结果
market=prices.merge(focus[['ts_code','name','area']],on='ts_code',how='inner',validate='many_to_one') # 合并行情与地区标签if focus.empty or market.empty: raise ValueError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 空对象或空连接时停止
if not np.isfinite(market[['close','vol']].to_numpy()).all() or market['close'].le(0).any(): raise ValueError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 行情必须有限且价格为正
if market.duplicated(['ts_code','trade_date']).any(): raise ValueError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 重复键时停止
market['trade_date']=pd.to_datetime(market['trade_date']) # 统一交易日期
market['month']=market['trade_date'].dt.to_period('M').astype(str) # 构造月度观察窗
if market['month'].nunique()<6: raise ValueError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 期间不足时停止趋势判断
market['return_rate']=market.groupby('ts_code')['close'].pct_change() # 计算公司日收益率
monthly=market.groupby(['month','area']).agg(companies=('ts_code','nunique'),mean_return=('return_rate','mean'),trading_volume=('vol','sum')).reset_index() # 输出月度地区聚合
sensitivity=monthly.assign(loose=monthly['mean_return'].abs()>0.02,baseline=monthly['mean_return'].abs()>0.03,strict=monthly['mean_return'].abs()>0.05,threshold_status='课堂情景,待业务规定批准',owner='物流分析负责人',action='核对异常月份',recheck_when='字段发生变化或分母为零',recheck_date='下月第5个工作日') # 比较三档课堂阈值并绑定说明信息
assert monthly['companies'].gt(0).all() # 核对每个聚合单元具有明确分母
print(focus[['ts_code','name','area']],monthly,sensitivity) # 输出对象、聚合与检查要求表case 路径是规定资产;不报分母;把行情代理解释成订单需求[商业大数据分析与应用]